package com.klikmee.sms.core.base.communicator.vip;

import java.net.URL;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import org.apache.log4j.Logger;

import com.klikmee.sms.core.base.Core;
import com.klikmee.sms.core.base.communicator.Communicator;
import com.klikmee.sms.core.base.communicator.DeliveryReport;
import com.klikmee.sms.core.base.communicator.SendSmsIntercepter;
import com.klikmee.sms.core.base.dao.ProviderFacadeLocal;
import com.klikmee.sms.core.base.util.Constants;
import com.klikmee.sms.core.base.util.DeliveryReports;
import com.klikmee.sms.core.model.BulkMessage;
import com.klikmee.sms.core.model.IncomingMessage;
import com.klikmee.sms.core.model.OutgoingMessage;
import com.klikmee.sms.core.model.Provider;
import com.klikmee.sms.core.ws.vip.VIPCommunicator;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.MessagePayload;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.MessageType;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.MessageType.WapPushServiceIndicated;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.MessageType.WapPushServiceLoaded;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.StatusType;
import com.klikmee.sms.core.ws.vip.client.org.tempuri.xmlschema.VaskoSecurityHeaderType;
import com.klikmee.sms.core.ws.vip.client.yu.co.vipmobile.xmlns._200711.VASKOInterface;
import com.klikmee.sms.core.ws.vip.client.yu.co.vipmobile.xmlns._200711.VASKOService;

public class VIPCommunicatorImpl implements Communicator, VIPCommunicator {

	/**
	 * Instance of logger obj
	 */
	public static final Logger			logger						= Logger.getLogger( VIPCommunicatorImpl.class );
	/**
	 * Core context responsible for further processing of incomming messages and
	 * status reports.
	 */
	private Core						core;
	private Provider					providerVIP;
	private ProviderFacadeLocal			providerDAO;

	// message types
	public static final String			MESSAGE_TYPE_TEXT			= "TEXT";
	public static final String			MESSAGE_TYPE_WAPPUSH_SI		= "WappushServiceIndicated";
	public static final String			MESSAGE_TYPE_WAPPUSH_SL		= "WappushServiceLoaded";
	public static final String			MESSAGE_TYPE_NOTIFICATION	= "Notification";
	public static final String			MESSAGE_TYPE_UNICODE		= "Unicode";
	public static final String			MESSAGE_TYPE_MMS			= "MMS";
	// security param values
	private VaskoSecurityHeaderType		vaskoSecurityHeaderType;

	// date formatter
	// date formatter used to decode date messages received from vip
	private SimpleDateFormat			vipDateFormat				= null;
	private String						vipDateFormatPattern		= "yyyy-MM-dd'T'hh:mm:ss";
	// date formatter used to encode messages sent to vip
	private SimpleDateFormat			ourDateFormat				= null;
	private String						ourDateFormatPattern		= "yyyy-MM-dd hh:mm:ss";
	// VASKO web service end point
	private VASKOInterface				vaskoEndpoint				= null;
	// timer used to schedule jobs for communicator such as periodically open
	// url connection
	// our id in vip database. used for bulk messages
	private String						vipProviderId				= null;
	/**
	 * VIP StatusType codes Status code indicating whether the operation was
	 * successful or failed. A status code of 200 represents a successful
	 * operation. Otherwise the Code will indicate an failure, and the
	 * Description attribute will contain further information.
	 * 
	 * DESCRIPTION Successful operation. Message was submitted. 200 Charging
	 * User Failed, Charging System Failed 100 Not Valid Charging Code 400
	 * General Exception, Timeout Exception, Correlation Failed Exception 300
	 * Wrong Format Billing ID, Wrong Communication State, Duplicate Billing 500
	 * ID
	 */
	private static final String			CHARGING_FAILED				= "100";
	private static final String			MESSAGE_SUBMITED			= "200";
	private static final String			GENERAL_EXCEPTION			= "300";
	private static final String			NOT_VALID_CHARGING_CODE		= "400";
	private static final String			WRONG_BILLINGID				= "500";
	private static final String			CUSTOMER_UNREACHABLE		= "600";
	private static final String			CUSTOMER_DOESNT_EXIST		= "700";
	private String						vaskoServiceEndpointAddress;
	private URL							wsdlURL;

	/**
	 * List of send sms interceptors used to prepare sms message for sending
	 */
	private List<SendSmsIntercepter>	sendSmsIntercepters			= null;

	/**
	 * inits the vip communicator. creates VASKO web service client and opens
	 * the connection to its endpoint. init is done in loop conditioned to
	 * circle around until init is executed successfully
	 */
	public void init() {

		// init vaskows client
		initVASKOWSClient();

		// init the date formatter
		this.vipDateFormat = new SimpleDateFormat( vipDateFormatPattern );
		this.ourDateFormat = new SimpleDateFormat( ourDateFormatPattern );
		// ConstantManager.CONF_VIP_PROVIDER_ID );
		// schedule url connection opener task
		this.providerVIP = providerDAO.findByName( Constants.PROVIDER_VIP );
		logger.debug( MessageFormat.format( "Loaded [{0}] provider for vip communicator", providerVIP ) );

		logger.info( "VIPCommunicator successfully started" );

	}

	private void initVASKOWSClient() {

		vaskoEndpoint = new VASKOService( wsdlURL, new QName( "http://xmlns.vipmobile.co.yu/200711", "VASKO_Service" ) ).getVASKOInterfaceEndpoint1();

		if (vaskoServiceEndpointAddress != null) {
			overrideEndpointAddress();

			logger.info( String.format( "Sendsms service url [%s]", this.vaskoServiceEndpointAddress ) );
		}

	}

	private void overrideEndpointAddress() {
		BindingProvider bindingProvider = (BindingProvider) vaskoEndpoint;
		Map<String, Object> requestContext = bindingProvider.getRequestContext();
		requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, vaskoServiceEndpointAddress );
	}

	/**
	 * invoked by smscore sends message to end user by invoking methods on vip
	 * vasko endpoint
	 * 
	 * @param OutgoingMessage
	 *            - message to be sent
	 */
	public void sendMessage(OutgoingMessage outgoingMessage) {

		// set our outgoing time for outgoingmessage
		outgoingMessage.setOurOutGoingTime( new Date( System.currentTimeMillis() ) );

		// build messagePayload
		MessagePayload messagePayload = new MessagePayload();

		// set charging code
		messagePayload.setChargingCode( outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory().getVipChargingCode() );

		// billing id is generated in this way: referenceid + "-" + message
		// serial number
		// outgoingmessage id is used as serial number
		String billingId = buildBillingId( outgoingMessage );
		messagePayload.setBillingID( billingId );

		// set message data
		MessageType messageType = buildMessageType( outgoingMessage );
		messagePayload.setMessage( messageType );

		try {
			// send message to customer
			vaskoEndpoint.deliverContentToCustomer( messagePayload, vaskoSecurityHeaderType );
			logger.info( MessageFormat.format( "OutgoingMesssage[{0}] sent, billingId[{1}], chargingCode[{2}], to [{3}]", outgoingMessage.getId(), billingId, messagePayload.getChargingCode(),
					messagePayload.getMessage().getReceiverNumber() ) );

			// notify core that message has been sent
			core.onMessageSent( outgoingMessage );
		}
		catch (Exception e) {
			String errorMessage = "Error sending message";
			logger.fatal( errorMessage, e );
		}

	}

	private String buildBillingId(OutgoingMessage outgoingMessage) {
		String billingId = null;
		int paymantType = outgoingMessage.getPaymentInvoiceList().iterator().next().getType();
		boolean isThisBulkMessage = outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory().getSubscription();

		/**
		 * in case of bulk message billing id is formed BillingID is created
		 * from the ContentProviderID (each Content provider will be assigned
		 * one ID by VIP), "-" (minus sign) and the unique serial number of the
		 * bulk SMS message generated by Content provider (i.e."10-12352",
		 * "10-12353", "10-12354"...).
		 */
		if (Constants.PI_TYPE_SMS_BULK == paymantType || isThisBulkMessage) {
			billingId = vipProviderId + "-" + outgoingMessage.getId();

		}
		/**
		 * BillingID is created from ReferenceID, "-" (minus sign) and the
		 * serial number of Content provider reply to this Customer request
		 * (i.e. if ReferenceID is "13245", BillingID would be "13245-1").
		 */
		else if (Constants.PI_TYPE_CHARGED_MESSAGE == paymantType) {

			billingId = outgoingMessage.getProvidersMesssageId();
		}

		return billingId;
	}

	public void sendBulkMessage(BulkMessage bulkMessage) {

		MessagePayload parameters = new MessagePayload();
		parameters.setChargingCode( bulkMessage.getChargingCategory().getVipChargingCode() );
		// set bulling id
		String billingId = vipProviderId + "-" + bulkMessage.getId();
		parameters.setBillingID( billingId );

		bulkMessage.setPrividersId( billingId );

		MessageType messageType = new MessageType();
		messageType.setSenderNumber( bulkMessage.getSenderId() );
		messageType.setReceiverNumber( bulkMessage.getToNumber() );
		messageType.setTimeStamp( ourDateFormat.format( bulkMessage.getSendingTime() ) );
		messageType.setTypeOfMessage( MESSAGE_TYPE_TEXT );
		messageType.setData( bulkMessage.getText() );

		parameters.setMessage( messageType );

		vaskoEndpoint.deliverContentToCustomer( parameters, this.vaskoSecurityHeaderType );

	}

	/**
	 * invoked by VASKOListener web service when new customer request is
	 * received. Translates MessagePayload object to IncomingMessage and
	 * forwards it to smscore
	 * 
	 * @param messagePayload
	 */
	public void onCustomerRequest(com.klikmee.sms.core.ws.vip.listener.Payload messagePayload) {

		// extract data from customer request
		String billingID = messagePayload.getBillingID();
		String chargingCode = messagePayload.getChargingCode();

		com.klikmee.sms.core.ws.vip.listener.MessageType messageType = messagePayload.getMessage();
		String data = messageType.getData();
		String receiverNumber = messageType.getReceiverNumber();
		String referenceId = messageType.getReferenceID();
		String senderNumber = messageType.getSenderNumber();
		String timestamp = messageType.getTimeStamp();

		int typeOfMessage = resolveVIPMessageType( messageType.getTypeOfMessage() );

		//

		// construct incomingMessage
		IncomingMessage incomingMessage = new IncomingMessage();
		incomingMessage.setFromNumber( senderNumber );
		incomingMessage.setToNumber( receiverNumber );
		incomingMessage.setMessage( data );
		incomingMessage.setTypeOfMessage( typeOfMessage );
		incomingMessage.setProvider( providerVIP );

		try {
			String timeStampWithoutTimezone = timestamp.substring( 0, timestamp.indexOf( "+" ) );
			incomingMessage.setProvidersIncomingTime( vipDateFormat.parse( timeStampWithoutTimezone ) );
		}
		catch (ParseException e) {
			e.printStackTrace();
		}

		incomingMessage.setProviderMessageId( referenceId );
		incomingMessage.setOurIncomingTime( new Date() );

		// forward message to core
		try {
			core.enqueueIncomingMessage( incomingMessage );
			// notify vipmobile that message is received successfully
			notifyMessageReceived( referenceId );
			logger.info( MessageFormat.format( "Customer request: referenceId[{0}], sender[{1}], receiver[{2}]", referenceId, senderNumber, receiverNumber ) );
		}
		catch (Exception e) {
			String errorMessage = "Error receiving customer request...";
			logger.fatal( errorMessage, e );
		}

	}

	private void notifyMessageReceived(String referenceId) {
		// notify vip that
		// message is successfuly received
		StatusType statusType = new StatusType();

		statusType.setCode( MESSAGE_SUBMITED );
		statusType.setReferenceID( referenceId );
		statusType.setDescription( "Message received successfuly" );
		statusType.setBillingID( "" );
		vaskoEndpoint.returnStatus( statusType, vaskoSecurityHeaderType );

		logger.info( MessageFormat.format( "Notify VASKO about message refId = [{0}], code = [{1}]", referenceId, statusType.getCode() ) );
	}

	@Override
	public boolean canHandleBulkMessage(String receiver) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * <p>
	 * invoked by VASKOListener web service when delivery status is received.
	 * Vasko sends billingId. In case of bulk it is
	 * <providersId>-<outgointMessageId in our db>. otherwise is
	 * <refernceId>-<replay order number> (32948732-1) this method creates
	 * OutgoingMessage object, updates it's state and forwards it to smscore for
	 * update in database.
	 * </p>
	 * 
	 * @param statusType
	 */
	public void onStatusReport(com.klikmee.sms.core.ws.vip.listener.StatusType statusType) {

		DeliveryReport outgoingMessage = new DeliveryReport();

		/**
		 * vip doesn't return reference id, instead returns billing id which
		 * contains reference id or outgoingMesageId. If billing id starts with
		 * providers id, then it's a status of bulk message. otherwise is't a
		 * status of message sent on users request so we will extract it
		 */
		String messageKey = null;

		String billingId = statusType.getBillingID();
		// if billing id starts with klikmee providers id then extract
		// outgoingMessageId
		if (billingId.startsWith( vipProviderId )) {
			messageKey = billingId.substring( billingId.indexOf( "-" ) + 1 );
			outgoingMessage.setMessageId( Long.parseLong( messageKey ) );
		} // otherwise use billingID
		else {
			// messageKey = billingId.substring(0, billingId.indexOf("-"));
			outgoingMessage.setProvidersMessageId( billingId );
		}

		int deliveryStatus = resolveDeliveryStaus( statusType.getCode() );

		outgoingMessage.setStatus( deliveryStatus );
		outgoingMessage.setProvider( providerVIP );

		try {
			core.enqueueStatusReport( outgoingMessage );
			logger.info( MessageFormat.format( "Received status report: billingId[{0}], statusCode[{1}], resolvedStatus[{2}], desc[{3}]", billingId, statusType.getCode(), deliveryStatus,
					statusType.getDescription() ) );
		}
		catch (Exception e) {
			logger.fatal( MessageFormat.format( "Error while processing status report, referenceId[{0}], code[{1}]", statusType.getReferenceID(), statusType.getCode() ), e );
		}

	}

	/**
	 * Helper method for translating VIP status codes into message states
	 * 
	 * @param vipStatus
	 * @return messageStatus if resolved, otherwise -1
	 */
	private int resolveDeliveryStaus(String vipStatus) {
		if (MESSAGE_SUBMITED.equals( vipStatus )) {
			return DeliveryReports.DELIVERED;
		}
		else if (VIPCommunicatorImpl.CHARGING_FAILED.equals( vipStatus )) {
			return DeliveryReports.Vip.CHARGING_FAILED;
		}
		else if (VIPCommunicatorImpl.GENERAL_EXCEPTION.equals( vipStatus )) {
			return DeliveryReports.Vip.GENERAL_EXCEPTION;
		}
		else if (VIPCommunicatorImpl.NOT_VALID_CHARGING_CODE.equals( vipStatus )) {
			return DeliveryReports.Vip.NOT_VALID_CHARGING_CODE;
		}
		else if (VIPCommunicatorImpl.WRONG_BILLINGID.equals( vipStatus )) {
			return DeliveryReports.Vip.WRONG_BILLINGID;
		}
		else if (VIPCommunicatorImpl.CUSTOMER_UNREACHABLE.equals( vipStatus )) {
			return DeliveryReports.Vip.CUSTOMER_UNREACHABLE;
		}
		else if (VIPCommunicatorImpl.CUSTOMER_DOESNT_EXIST.equals( vipStatus )) {
			return DeliveryReports.Vip.CUSTOMER_DOESNT_EXIST;
		}
		else {
			return -1;
		}
	}

	/**
	 * Retruns a list of {@link SendSmsIntercepter} for this communicator
	 */
	@Override
	public List<SendSmsIntercepter> getSendSmsIntercepters() {

		return this.sendSmsIntercepters;
	}

	private MessageType buildMessageType(OutgoingMessage outgoingMessage) {

		MessageType messageType = new MessageType();
		/**
		 * set sender & receiver numbers, time stamp reference id they are not
		 * type specific
		 */
		messageType.setSenderNumber( outgoingMessage.getFromNumber() );
		messageType.setReceiverNumber( outgoingMessage.getToNumber() );
		messageType.setTimeStamp( ourDateFormat.format( outgoingMessage.getOurOutGoingTime() ) );

		// referenceId depends on is message bulk or reply to customers request
		// if message is bulk than referenceid is <providersid>-<id of message
		// in our db>
		// if message is replay to customer request then <referenceId>-<order
		// number of our replay to this request>

		int paymentType = outgoingMessage.getPaymentInvoiceList().iterator().next().getType();
		boolean isBulkMessage = outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory().getSubscription();

		messageType.setReferenceID( (paymentType == Constants.PI_TYPE_SMS_BULK) || (isBulkMessage) ? "" : outgoingMessage.getProvidersMesssageId().substring( 0,
				outgoingMessage.getProvidersMesssageId().indexOf( "-" ) ) );

		/**
		 * set type specific params text,
		 */
		Integer typeOfOutMessage = outgoingMessage.getTypeOfMessage();
		switch (typeOfOutMessage) {

		case Constants.MESSAGE_TYPE_SMS:
			messageType.setTypeOfMessage( MESSAGE_TYPE_TEXT );
			messageType.setData( outgoingMessage.getMessage() );

			break;
		case Constants.MESSAGE_TYPE_SMS_UNICODE:
			messageType.setTypeOfMessage( MESSAGE_TYPE_UNICODE );

			messageType.setData( outgoingMessage.getMessage() );

			break;

		case Constants.MESSAGE_TYPE_MMS:
			messageType.setTypeOfMessage( MESSAGE_TYPE_MMS );

			break;

		case Constants.MESSAGE_TYPE_WAP_PUSH_SERVICE_INDICATED:
			messageType.setTypeOfMessage( MESSAGE_TYPE_WAPPUSH_SI );
			WapPushServiceIndicated wapPushServiceIndicated = new WapPushServiceIndicated();
			wapPushServiceIndicated.setURL( outgoingMessage.getUrl() );
			wapPushServiceIndicated.setDescription( outgoingMessage.getMessage() );
			messageType.setWapPushServiceIndicated( wapPushServiceIndicated );

			break;

		case Constants.MESSAGE_TYPE_WAP_PUSH_SERVICE_LOADED:
			messageType.setTypeOfMessage( MESSAGE_TYPE_WAPPUSH_SL );
			WapPushServiceLoaded wapPushServiceLoaded = new WapPushServiceLoaded();
			wapPushServiceLoaded.setURL( outgoingMessage.getUrl() );

			messageType.setWapPushServiceLoaded( wapPushServiceLoaded );

			break;

		case Constants.MESSAGE_TYPE_FLASH:
			messageType.setTypeOfMessage( MESSAGE_TYPE_NOTIFICATION );
			messageType.setData( outgoingMessage.getMessage() );
		default:
			break;
		}

		return messageType;
	}

	private int resolveVIPMessageType(String vipMessageType) {
		if (MESSAGE_TYPE_TEXT.equalsIgnoreCase( vipMessageType )) {
			return Constants.MESSAGE_TYPE_SMS;
		}
		else if (MESSAGE_TYPE_MMS.equalsIgnoreCase( vipMessageType )) {
			return Constants.MESSAGE_TYPE_MMS;
		}
		else if (MESSAGE_TYPE_NOTIFICATION.equalsIgnoreCase( vipMessageType )) {
			return Constants.MESSAGE_TYPE_FLASH;
		}
		else if (MESSAGE_TYPE_WAPPUSH_SI.equalsIgnoreCase( vipMessageType ) || MESSAGE_TYPE_WAPPUSH_SL.equalsIgnoreCase( vipMessageType )) {
			// return ConstantManager.MESSAGE_TYPE_WAP_PUSH;
		}
		return -1;
	}

	public void setVipProviderId(String vipProviderId) {
		this.vipProviderId = vipProviderId;
	}

	public void setCore(Core core) {
		this.core = core;
	}

	public void setProviderDAO(ProviderFacadeLocal providerDAO) {
		this.providerDAO = providerDAO;
	}

	public void setVaskoSecurityHeaderType(VaskoSecurityHeaderType vaskoSecurityHeaderType) {
		this.vaskoSecurityHeaderType = vaskoSecurityHeaderType;
	}

	public void setVaskoServiceEndpointAddress(String vaskoServiceendpointAddress) {
		this.vaskoServiceEndpointAddress = vaskoServiceendpointAddress;
	}

	public void setWsdlURL(URL wsdlURL) {
		this.wsdlURL = wsdlURL;
	}

	public void setSendSmsIntercepters(List<SendSmsIntercepter> sendSmsIntercepters) {
		this.sendSmsIntercepters = sendSmsIntercepters;
	}

	@Override
	public Provider getProvider() {
		// TODO Auto-generated method stub
		return null;
	}

}
